﻿using System;
using System.Reflection;

namespace Kyoh.Lib.Component.Arguments
{
    public partial class ArgumentPropertyAttribute
    {
        private class ArgumentParser
        {
            private ArgumentPropertyAttribute attribute;
            public ArgumentParser(ArgumentPropertyAttribute attribute)
            {
                this.attribute = attribute;
            }

            #region ArgumentPropertyAttribute メンバー

            public string Argument(object obj)
            {
                return this.attribute.Argument(obj);
            }
            public string DetailUsage(int prefixLength, int keyLength, int separatorLength)
            {
                return this.attribute.DetailUsage(prefixLength, keyLength, separatorLength);
            }

            public string Usage()
            {
                return this.attribute.Usage();
            }

            public void Reffer(ArgumentObjectAttribute obj, System.Reflection.PropertyInfo prop)
            {
                this.attribute.Reffer(obj, prop);
            }

            public bool Match(string argumentName)
            {
                return this.attribute.Match(argumentName);
            }

            public string Abbreviation { get { return this.attribute.Abbreviation; } }
            public string ArgumentName { get { return this.attribute.ArgumentName; } }
            public bool? IgnoreCase { get { return this.attribute.IgnoreCase; } }
            public string Key { get { return this.attribute.Key; } }
            public int KeyLength { get { return this.attribute.KeyLength; } }
            public string Prefix { get { return this.attribute.Prefix; } }
            public int PrefixLength { get { return this.attribute.PrefixLength; } }
            public System.Reflection.PropertyInfo Property { get { return this.attribute.Property; } }
            public Type PropertyType { get { return this.attribute.PropertyType; } }
            public bool Required { get { return this.attribute.Required; } }
            public string Separator { get { return this.attribute.Separator; } }
            public int SeparatorLength { get { return this.attribute.SeparatorLength; } }
            public bool IsReffered { get { return this.attribute.IsReffered; } }

            #endregion

            public bool Set { get; private set; }

            public void Reset()
            {
                this.Set = false;
            }

            /// <summary>引数文字列を元に、引数オブジェクトに値を設定します。</summary>
            /// <param name="arg">引数文字列を指定します。</param>
            /// <param name="obj">値を設定する引数オブジェクトを指定します。</param>
            /// <exception cref="InvalidArgumentException">引数が重複しているか、変換できない値が含まれています。</exception>
            public bool Parse(string arg, object obj)
            {
                if (!string.IsNullOrEmpty(arg) && arg.Length > 1 && Array.Exists(this.attribute.prefixes, x => x == arg[0]))
                {
                    var kv = arg.Substring(1).Split(this.attribute.separators, 2, StringSplitOptions.RemoveEmptyEntries);
                    var key = kv[0];
                    if (this.Match(key))
                    {
                        if (this.Set)
                            throw new InvalidArgumentException(ArgumentInvalidity.Duplicate) { ArgumentName = key, PropertyName = this.Property.Name };
                        else
                        {
                            try
                            {
                                if (this.PropertyType == typeof(bool))
                                {

                                    this.Property.SetValue(obj, !(bool)this.Property.GetValue(obj, null), null);
                                }
                                else if (kv.Length > 1)
                                    this.Property.SetValue(obj, this.attribute.converter.ConvertFromString(kv[1]), null);
                                else if (this.PropertyType.IsSubclassOf(typeof(ValueType)))
                                    this.Property.SetValue(obj, this.attribute.constructor.Invoke(null), null);
                                else
                                    this.Property.SetValue(obj, null, null);
                                this.Set = true;
                                return true;
                            }
                            catch (TargetException) { /* obj が null の状態で、インスタンスメンバに値を設定しようとした場合、無視する */ }
                            catch (Exception exc) { throw new InvalidArgumentException(ArgumentInvalidity.Parsing, Properties.ExceptionMessage.Parsing, exc) { ArgumentName = key, PropertyName = this.Property.Name }; }
                        }
                    }
                }
                return false;
            }

        }
    }
}